package be.nepherte.movmanager.client.ui.components;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.HTMLTable.Cell;
import com.google.gwt.user.client.ui.HTMLTable.ColumnFormatter;
import com.google.gwt.user.client.ui.HTMLTable.RowFormatter;
import com.google.gwt.user.client.ui.Image;

import be.nepherte.movmanager.client.MovManager;
import be.nepherte.movmanager.client.util.StringUtil;
import be.nepherte.movmanager.domain.Movie;

/**
 * This class represents a sortable, navigatable table for movie lists. The
 * first row is a header containing a movie attribute in each column, the other
 * rows represent movies. It also features displaying a limited list of movies
 * instead of the entire list.
 * 
 * <p>
 * Copyright © 2010 Bart Verhoeven
 * </p>
 * 
 * <p>
 * This file is part of MovManager.
 * </p>
 * 
 * <p>
 * MovManger is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * </p>
 * 
 * <p>
 * MovManger is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 * </p>
 * 
 * <p>
 * You should have received a copy of the GNU General Public License along with
 * MovManger. If not, see http://www.gnu.org/licenses/.
 * </p>
 * 
 * @author	Bart Verhoeven
 * @version 0.1
 */
public class MovieListView extends AbstractMoviesView implements ClickHandler {
	/**Sortable table holding the movies.*/
	private SortableTable table = new SortableTable();
	/**A image button place holder for the left arrow to navigate through the movie table list.*/
	private final Image btnPrev = new Image(GWT.getModuleBaseURL() + "images/arrow_blank.png");
	/**A image button place holder for the right arrow to navigate through the movie table list.*/
	private final Image btnNext = new Image(GWT.getModuleBaseURL() + "images/arrow_blank.png");
	
	/**
	 * Creates a new MovieListTable. Only movies starting at <tt>firstMovieIndex</tt>
	 * and ending at <tt>firstMovieIndex+maxVisibleEntries-1</tt> in the given movie
	 * list are displayed. It is possible that fewer than <tt>maxVisibleEntries</tt> 
	 * movies are displayed, e.g when there are not that many movies.
	 * 
	 * @param	movies
	 *          list of movies
	 * @param	firstMovieIndex
	 *          index of the first movie to be displayed
	 * @param	maxVisibleEntries
	 * 			the maximum number of visible movies shown at once
	 */
	private MovieListView(List<Movie> movies, int firstMovieIndex, int maxVisibleEntries) {
		super(movies);
		btnPrev.addClickHandler(this);
		btnNext.addClickHandler(this);
		createMovieListHeader();
		fillTableWithMovies(movies);
		displayMovies(firstMovieIndex, maxVisibleEntries, maxVisibleEntries);
		initWidget(table);
	}
	
	/**
	 * Creates a new MovieListTable with a given maximum number of visual entries.
	 * 
	 * @param	maxVisibleEntries
	 * 			the maximum number of visible movies shown at once
	 */
	public MovieListView(int maxVisibleEntries) {
		this(new ArrayList<Movie>(),0, maxVisibleEntries);
	}
	
	/**
	 * Creates a new MovieListTable with a given maximum number of visual entries.
	 * The table is filled with the given movie list.
	 * 
	 * @param	movies
	 *          list of movies
	 * @param	maxVisibleEntries
	 * 			the maximum number of visible movies shown at once
	 */
	public MovieListView(List<Movie> movies, int maxVisibleEntries) {
		this(movies, 0, maxVisibleEntries);
	}
	
	/**
	 * Creates a new MovieListTable. All movies will be visible in the table.
	 * 
	 * @param	movies
	 *          the list of movies this table contains
	 */
	public MovieListView(List<Movie> movies) {
		this(movies,0,movies.size());
	}
	
	@Override
	public void setMaxVisibleElements(int newCapacity) {
		int oldCapacity = this.maxVisibleEntries;
		displayMovies(firstMovieIndex, oldCapacity, newCapacity);
	}
	
	@Override
	public void setMovies(List<Movie> movieList) {
		this.movies = movieList;
		fillTableWithMovies(movieList);
		displayMovies(0,maxVisibleEntries, maxVisibleEntries);
	}
	
	@Override
	public void setMovies(List<Movie> movieList, int firstMovieIndex) {
		this.movies = movieList;
		fillTableWithMovies(movieList);
		displayMovies(firstMovieIndex, maxVisibleEntries, maxVisibleEntries);
	}
	
	/**
	 * Creates a header for this movie list table.
	 */
	private void createMovieListHeader() {
		// Set header
		table.addColumnHeader("#", 0);
		table.addColumnHeader(MovManager.constants.movieTitle(), 1);
		table.addColumnHeader(MovManager.constants.movieDirectors(), 2);
		table.addColumnHeader(MovManager.constants.movieDuration(), 3);
		table.addColumnHeader(MovManager.constants.movieRating(), 4);
		table.addColumnHeader(MovManager.constants.movieGenres(), 5);
		table.addColumnHeader(MovManager.constants.movieReleaseDate(), 6);
		table.addColumnHeader(MovManager.constants.movieHasWatched(), 7);

		// Format movies table
		table.getRowFormatter().setStyleName(0, "moviesTableHeader");
		ColumnFormatter columnFormatter = table.getColumnFormatter();
		columnFormatter.setWidth(0, "15px");
		columnFormatter.setWidth(1, "250px");
		columnFormatter.setWidth(2, "150px");
		columnFormatter.setWidth(3, "15px");
		columnFormatter.setWidth(4, "15px");
		columnFormatter.setWidth(5, "200px");
		columnFormatter.setWidth(6, "15px");
		columnFormatter.setWidth(7, "15px");
		table.setCellSpacing(10);
	}
	
	/**
	 * Fills this movie list table with the given movies. Any previous movie is
	 * removed. The movies are not displayed.
	 * 
	 * @param	movies
	 *          new list of movies
	 */
	private void fillTableWithMovies(List<Movie> movies) {
		if (movies == null)
			return;
		
		RowFormatter rowFormatter = table.getRowFormatter();
		
		// Fill table with new movies
		int movieSize = movies.size();
		for (int i = 1; i <= movieSize; i++) {
			Movie mov = movies.get(i - 1);
			String title = mov.getTitle();
			int duration = mov.getDuration();
			String directors = StringUtil.toString(mov.getDirectors(), ", ");
			String genres = StringUtil.toString(mov.getGenres(), " | ");
			Date releaseDate = mov.getReleaseDate();
			boolean hasWatched = mov.hasWatched();
			Image info = new Image(GWT.getModuleBaseURL() + "images/info.png");
			info.setTitle("View movie details");
			info.addClickHandler(this);
			table.setValue(i, 0, i);
			table.setValue(i, 1, title);
			table.setValue(i, 2, !directors.equals("") ? directors : "-");
			table.setValue(i, 3, duration != 0 ? duration : "-");
			table.setValue(i, 4, mov.getRating() != 0 ? mov.getRating() : "-");
			table.setValue(i, 5, !genres.equals("") ? genres : "-");
			table.setValue(i, 6, releaseDate != null ? DateTimeFormat.getFormat("y").format(releaseDate) : "-");
			table.setValue(i, 7, hasWatched ? MovManager.constants.yes() : MovManager.constants.no());
			table.setWidget(i, 8, info);
			rowFormatter.setVisible(i, false);
		}
		
		// remove old rows that were not overwritten by the above
		int numRows = table.getRowCount();
	    for (int i = movieSize+1; i < numRows; i++) {
	      table.removeRow(table.getRowCount()-1);
	    }
	}

	/**
	 * Displays movies starting at <tt>startIndex</tt> and ending at
	 * <tt>startIndex+newCapacity-1</tt>. All other movies are hidden.
	 * 
	 * @param	startIndex
	 *          index of the first movie in movie list that need to be visible.
	 * @param	oldCapacity
	 * 			grid's old capacity
	 * @param	newCapacity
	 * 			grid's new capacity
	 */
	private void displayMovies(int startIndex, int oldCapacity, int newCapacity) {
		if (movies == null)
			return;
		
		RowFormatter rowFormatter = table.getRowFormatter();
		
		// Hide old rows
		for (int i=firstMovieIndex; i < firstMovieIndex+oldCapacity && i < movies.size() ; i++)
			rowFormatter.setVisible(i+1, false);
		
		// Set new rows visible
		for (int i=startIndex; i < startIndex+newCapacity; i++)
			rowFormatter.setVisible(i+1, true);
		
		this.firstMovieIndex = startIndex;
		this.maxVisibleEntries = newCapacity;
		updateNavigationArrows();
	}
	
	@Override
	public void navigateLeft() {
		// we can't go back
		if (firstMovieIndex == 0)
			return;
		
		int diff = firstMovieIndex-maxVisibleEntries;
		int newFirstMovieIndex = diff >= 0 ? diff : 0;
		displayMovies(newFirstMovieIndex, maxVisibleEntries, maxVisibleEntries);
	}
	

	@Override
	public void navigateRight() {
		// we can't go any further
		if (firstMovieIndex == movies.size()-1)
			return;
			
		int diff = firstMovieIndex+maxVisibleEntries;
		int movieSize = movies.size();
		int newFirstMovieIndex = diff <= movieSize ? diff : movieSize;
		displayMovies(newFirstMovieIndex,maxVisibleEntries, maxVisibleEntries);
	}
	
	/**
	 * Updates the navigation arrows.
	 */
	private void updateNavigationArrows() {
		if (movies == null)
			return;
		
		int nextMax = firstMovieIndex+2*maxVisibleEntries > movies.size() ? movies.size() : firstMovieIndex+2*maxVisibleEntries;
		int prevMin = firstMovieIndex-maxVisibleEntries <= 0 ? 1 : firstMovieIndex-maxVisibleEntries;
		
		// update left arrow
		if (firstMovieIndex > 0) {
			btnPrev.setUrl(GWT.getModuleBaseURL() + "images/arrow_left.png");
			btnPrev.setTitle("View results " + prevMin + "-" + firstMovieIndex + " of " + movies.size());
		}
		else {
			btnPrev.setUrl(GWT.getModuleBaseURL() + "images/arrow_blank.png");
			btnPrev.setTitle(null);
		}
		
		// update right arrow
		if (firstMovieIndex+maxVisibleEntries < movies.size()) {
			btnNext.setUrl(GWT.getModuleBaseURL() + "images/arrow_right.png");
			btnNext.setTitle("View results " + (firstMovieIndex+ maxVisibleEntries+1) + "-" + nextMax + " of " + movies.size());
		}
		else {
			btnNext.setUrl(GWT.getModuleBaseURL() + "images/arrow_blank.png");
			btnNext.setTitle(null);
		}
	}
	
	/**
	 * Called when a click event is fired by one of the clickable components in
	 * this tab.
	 */
	@Override
	public void onClick(ClickEvent event) {
		Object source = event.getSource();
		
		//  the left navigation arrow is clicked
		if (source.equals(btnPrev) && !btnPrev.getUrl().contains("blank")) {
			navigateLeft();
			return;
		}
		
		// the right navigation arrow is clicked
		if (source.equals(btnNext) && !btnNext.getUrl().contains("blank")) {
			navigateRight();
			return;
		}
		
		// some info button is clicked
		if (source instanceof Image && ((Image) source).getUrl().contains("info")) {
			Cell c = table.getCellForEvent(event);
			ArrayList<Movie> movies = new ArrayList<Movie>();
			movies.add(super.movies.get(c.getRowIndex()-1));
			DialogBox pp = new DialogBox(true);
			pp.setGlassEnabled(true);
			pp.setPixelSize(800,575);
			pp.setWidget(new MovieDetailsView(movies));
			pp.center();
			return;
		}
			
		// pass on other click events to SortableTable
		table.onClick(event);
	}
	
	@Override
	public Image getLeftNavigationElement() {
		return btnPrev;
	}
	
	@Override
	public Image getRightNavigationElement() {
		return btnNext;
	}
}